use std::error::Error;
use std::str::FromStr;

use futures::StreamExt;
use libp2p::gossipsub::{GossipsubEvent, IdentTopic};
use libp2p::mdns::MdnsEvent;
use libp2p::swarm::SwarmEvent;
use libp2p::{identity, Multiaddr};
use libp2p::{PeerId, Swarm};
use tauri::async_runtime::Receiver;
use tokio::select;

use crate::behavior::{MyBehaviour, MyBehaviourEvent};
use crate::command::Command;
use crate::kadevents::process_kad_events;
use crate::SwarmOptions;

// boot nodes for kad
const BOOTNODES: [(&str /*peer_id */, &str /*multiaddr */); 2] = [
  (
    "QmdVoz8Y6QfKxvQ7nuC37JduuoAekeYDnzL46mBKa42XNM",
    "/ip4/148.70.58.15/tcp/4001",
  ),
  (
    "QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",
    "/ip4/104.131.131.82/tcp/4001",
  ),
  // (
  //   "QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
  //   "/dnsaddr/bootstrap.libp2p.io",
  // ),
  // (
  //   "QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa",
  //   "/dnsaddr/bootstrap.libp2p.io",
  // ),
  // (
  //   "QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb",
  //   "/dnsaddr/bootstrap.libp2p.io",
  // ),
  // (
  //   "QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt",
  //   "/dnsaddr/bootstrap.libp2p.io",
  // ),
];

pub async fn run_swarm<R: tauri::Runtime>(
  window: tauri::Window<R>,
  mut rx: Receiver<Command>,
) -> Result<(), Box<dyn Error>> {
  // Create a random PeerId
  let local_key = identity::Keypair::generate_ed25519();
  let local_peer_id = PeerId::from(local_key.public());
  trace!("Local peer id: {}", local_peer_id);

  let transport = libp2p::development_transport(local_key.clone()).await?;

  let mut swarm_options = SwarmOptions {
    keypair: local_key.clone(),
    bootstrap: Vec::new(),
    peer_id: local_peer_id.clone(),
  };

  for peer in &BOOTNODES {
    swarm_options
      .bootstrap
      .push((PeerId::from_str(peer.0)?, Multiaddr::from_str(peer.1)?));
  }

  // Create a Gossipsub topic
  let mut the_topic = Option::None;

  // Create a Swarm to manage peers and events
  let mut swarm = {
    let behaviour = MyBehaviour::new(swarm_options).await?;
    Swarm::new(transport, behaviour, local_peer_id)
  };

  swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?;
  match swarm.behaviour_mut().kademlia.bootstrap() {
    Ok(id) => {
      trace!("kad: boostrap the node: {:?}", id);
    }
    Err(e) => {
      trace!("kad: can't bootstrap the node: {:?}", e);
    }
  }

  trace!("using Gossipsub ... ");
  loop {
    select! {
        Some(cmd) = rx.recv() => {
          match cmd {
            Command::SendMessage { message } => {
              if let Some(topic) = the_topic.clone() {
                if let Err(e) = swarm.behaviour_mut().gossipsub.publish(topic, message.as_bytes()) {
                  trace!("Publish error: {:?}", e);
                }
              }
            }
            Command::Subscribe { topic } => {
              let topic = IdentTopic::new(format!("tauri-plugin-libp2p-signal: {}", topic));
              let _ = swarm.behaviour_mut().gossipsub.subscribe(&topic);
              the_topic = Some(topic);
            }
            Command::Unsubscribe { topic } => {
              let topic = IdentTopic::new(format!("tauri-plugin-libp2p-signal: {}", topic));
              let _ = swarm.behaviour_mut().gossipsub.unsubscribe(&topic);
              the_topic = Option::None;
            }
          }
        },
        event = swarm.select_next_some() => match event {
            SwarmEvent::Behaviour(MyBehaviourEvent::Mdns(MdnsEvent::Discovered(list))) => {
                for (peer_id, _multiaddr) in list {
                  trace!("mDNS discovered a new peer: {}", peer_id);
                    swarm.behaviour_mut().gossipsub.add_explicit_peer(&peer_id);
                    window.emit("plugin:libp2p-signal:found-peer", peer_id.to_string()).unwrap();
                }
            },
            SwarmEvent::Behaviour(MyBehaviourEvent::Mdns(MdnsEvent::Expired(list))) => {
                for (peer_id, _multiaddr) in list {
                  trace!("mDNS discover peer has expired: {}", peer_id);
                    swarm.behaviour_mut().gossipsub.remove_explicit_peer(&peer_id);
                }
            },
            SwarmEvent::Behaviour(MyBehaviourEvent::Gossipsub(GossipsubEvent::Message {
                propagation_source: _peer_id,
                message_id: _id,
                message,
            })) => {
                // trace!(
                //     "Got message: '{}' with id: {id} from peer: {peer_id}",
                //     String::from_utf8_lossy(&message.data),
                // );
                window.emit("plugin:libp2p-signal:got-message", String::from_utf8_lossy(&message.data)).unwrap();
            },
            SwarmEvent::Behaviour(MyBehaviourEvent::Kademlia(event)) => {
              process_kad_events(&swarm.behaviour_mut().kademlia, event);
            },
            _ => {}
        },
    }
  }
}
